#import "PersistentDataCollection.h"

/**
 * A PersistentDataCollection is an object representing a
 * collection of data types that are stored on disk.
 *
 * All objects in the collection must conform to the 
 * PersistentDataType protocol.
 *
 * @author      James Lin
 * @version     0.7.5
 * @since       0.3.2
 * @see         PersistentDataType
 */
@implementation PersistentDataCollection

@synthesize path;

/**
 * Initialize the PersistentDataCollection as an empty collection.
 * 
 * @param fileName  the name of the file in which to store this collection
 * @param subdir    the unique subdirectory holding all data for this collection
 * @return          the initialized empty PersistentDataCollection
 */
- (id)initWithEmptyFile:(NSString*)fileName 
	  usingSubdirectory:(NSString*)subdir {
	if (self = [super init]) {
		[self resetArchiveWithFileName:fileName 
					 usingSubdirectory:subdir];
	}
	
	return self;
}

/**
 * Initialize the PersistentDataCollection with existing persistent data.
 * 
 * If the file at the path is non-existent, one is created.
 *
 * @precondition    the file at the path is valid or non-existent
 * @param fileName  the path from which to load or store this collection
 * @param subdir    the unique subdirectory holding all data for this collection
 * @return          the initialized PersistentDataCollection
 */
- (id)initWithFile:(NSString*)fileName usingSubdirectory:(NSString*)subdir {
	if (self = [super init]) {
		NSFileManager* fileManager = [NSFileManager defaultManager];
		
		// Get the absolute path for the archive
		self.path = [FileUtilities getPathWithFileName:fileName 
									 usingSubdirectory:subdir];
		if ([fileManager fileExistsAtPath:path]) {
			[self loadData];
		} else {
			[self resetArchiveWithFileName:fileName 
						 usingSubdirectory:subdir];
		}
	}
	
	return self;
}

/**
 * Initialize the PersistentDataCollection with an array of data types.
 * 
 * @precondition     the data types are all valid and unique
 * @param dataTypes  the array of data types with which to initialize
 * @param fileName   the path at which to store this collection
 * @param subdir     the unique subdirectory holding all data for this collection
 * @return           the initialized PersistentDataCollection with specified data types
 */
- (id)initWithDataTypes:(NSArray*)dataTypes 
				   file:(NSString*)fileName 
	  usingSubdirectory:(NSString*)subdir {
	if (self = [super init]) {
		dataDictionary = [[NSMutableDictionary alloc] init];
		[self addDataTypes:dataTypes];
		self.path = [FileUtilities getPathWithFileName:fileName 
									 usingSubdirectory:subdir];
		[self saveData];
	}
	
	return self;
}

/**
 * Create a new empty archive.
 *
 * @param fileName  the path at which to store this collection
 * @param subdir    the unique subdirectory holding all data for this collection
 */
- (void)resetArchiveWithFileName:(NSString*)fileName
			   usingSubdirectory:(NSString*)subdir {
	dataDictionary = [[NSMutableDictionary alloc] init];
	self.path = [FileUtilities getPathWithFileName:fileName 
								 usingSubdirectory:subdir];
	[self saveData];
}

/**
 * Retrieve the unique key used for archiving.
 *
 * Override to specify unique key if subclassing.
 *
 * @return  the key used for keyed archiving
 */
- (NSString*)getArchiveKey {
	return @"ArchiveKey";
}

/**
 * Save the event collection data to archive.
 *
 * Sets the root object of the tree and recursively encodes every
 * persistent data type in the dictionary to archive.
 */
- (void)saveData {
	NSMutableDictionary *rootObject = [NSMutableDictionary dictionary];
	[rootObject setValue:dataDictionary forKey:[self getArchiveKey]];
	[NSKeyedArchiver archiveRootObject:rootObject toFile:path];
}

/**
 * Load the event collection data from archive.
 *
 * Sets the root object of the tree and recursively decodes every
 * persistent data type in the archived dictionary.
 */
- (void)loadData {
	NSMutableDictionary* rootObject;
	rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
	dataDictionary = [[rootObject valueForKey:[self getArchiveKey]] retain];
}

/**
 * Add a persistent data type to the collection.
 * 
 * @precondition    the data type is unique in the collection
 * @param dataType  the data type to add
 */
- (void)addDataType:(id<PersistentDataType>)dataType {
	[dataDictionary setObject:dataType forKey:[dataType getKey]];
	[self saveData];
}

/**
 * Add an array of persistent data types to the collection.
 *
 * @precondition     the data types are all unique in the collection
 * @param dataTypes  the array of data types to add
 */
- (void)addDataTypes:(NSArray*)dataTypes {
	for (NSObject* dataType in dataTypes) {
		[self addDataType:(id<PersistentDataType>)dataType];
	}
	[self saveData];
}

/**
 * Add another persistent data collection to this collection.
 *
 * @param dataCollection  the persistent data collection to add to this collection
 */
- (void)addDataCollection:(PersistentDataCollection*)dataCollection {
	[self addDataTypes:[dataCollection getDataTypes]];
}

/**
 * Get the data types in the given list of data types that are in 
 * this collection.
 *
 * @param dataTypes  the list of data types of which to check intersection
 * @return           the data types in the given list that are in this collection
 */
- (NSArray*)intersectDataTypes:(NSArray*)dataTypes {
	NSMutableArray* intersectingDataTypes = [NSMutableArray array];
	
	for (id<PersistentDataType> dataType in dataTypes) {
		if ([self containsDataType:dataType]) {
			[intersectingDataTypes addObject:dataType];
		}
	}
	
	return intersectingDataTypes;
}

/**
 * Determine whether the given data type is in the collection.
 *
 * @param dataType  the data type of which to check existence
 * @return          YES if the data type is in the collection;
 *                  NO otherwise.
 */
- (BOOL)containsDataType:(id<PersistentDataType>)dataType {
	return ([dataDictionary valueForKey:[dataType getKey]] != nil);
}

/**
 * Retrieve size of collection.
 *
 * @return  the size of the collection as an integer
 */
- (int)size {
	return [dataDictionary count];
}

/**
 * Retrieve all data types in the collection.
 *
 * @return  an array of all data types in the collection
 */
- (NSArray*)getDataTypes {
	return [dataDictionary allValues];
}

/**
 * Find all data types whose keys contain any or all of the keywords 
 * in the given space-delimited keyword string.
 *
 * @precondition    keyword string is not nil
 * @param keyword   the case-insensitive search keyword string
 * @param matchAll  YES if all keywords in the keyword list must match;
 *                  NO if any keyword can match.
 * @return          an array of data types with suitable matches
 */
- (NSArray*)findDataTypesWithKeywordString:(NSString*)keyword 
						  matchAllKeywords:(BOOL)matchAll {
	return [self findDataTypesWithKeywords:[keyword componentsSeparatedByString:@" "] 
						  matchAllKeywords:NO];
}

/**
 * Find all data types whose keys contain any or all of the keywords
 * in the given list of search keywords.
 *
 * If matching by any keyword in the keyword list, an additional sort
 * will be performed on the resulting list after the completed query.
 * The resulting list of events will be in non-increasing order of 
 * the number of keywords matched.
 *
 * @precondition    keywords are not nil
 * @param keywords  the list of case-insensitive search keywords
 * @param matchAll  YES if all keywords in the keyword list must match;
 *                  NO if any keyword can match.
 * @return          an array of data types with suitable matches
 */
- (NSArray*)findDataTypesWithKeywords:(NSArray*)keywords 
					 matchAllKeywords:(BOOL)matchAll {
	NSMutableArray* matchedDataTypes = [NSMutableArray array];
	
	if (matchAll) { // Match all keywords search mode
		for (NSString* key in [dataDictionary allKeys]) { // Search each data type
			BOOL matched = YES;
			
			for (NSString* keyword in keywords) {
				// Attempt to match each keyword to the selected data type
				if ([key rangeOfString:keyword 
							   options:NSCaseInsensitiveSearch].length == 0) {
					// Keyword not matched
					matched = NO;
					break;
				}
			}
			
			if (matched) { // Data type matched all keywords
				[matchedDataTypes addObject:[dataDictionary valueForKey:key]];
			}
		}
		return matchedDataTypes;
		
	} else { // Match any keywords search mode
		NSMutableArray* numKeywordsMatched = [NSMutableArray array];
		
		for (NSString* key in [dataDictionary allKeys]) { // Search each data type
			int keywordCount = 0;
			
			for (NSString* keyword in keywords) {
				// Attempt to match each keyword to the selected data type
				if ([key rangeOfString:keyword 
							   options:NSCaseInsensitiveSearch].length > 0) {
					keywordCount++;
				}
			}
			
			if (keywordCount > 0) { // Data type matched at least one keyword
				[numKeywordsMatched addObject:[NSNumber numberWithInt:keywordCount]];
				[matchedDataTypes addObject:[dataDictionary valueForKey:key]];
			}
		}
		
		return [matchedDataTypes revSortedArrayUsingOtherArray:numKeywordsMatched];
	}
}

/**
 * Retrieve the data type with the given key.
 *
 * @param key  the key of the data type to retrieve
 */
- (id<PersistentDataType>)getDataTypeWithKey:(NSString*)key {
	return [dataDictionary valueForKey:key];
}

/**
 * Remove a persistent data type from the collection.
 *
 * @param dataType  the dataType to remove
 */
- (void)removeDataType:(id<PersistentDataType>)dataType {
	if (dataType != nil) {
		[dataDictionary removeObjectForKey:[dataType getKey]];
	}
	[self saveData];
}

/**
 * Empty the collection.
 */
- (void)clear {
	[dataDictionary removeAllObjects];
	[self saveData];
}

/**
 * Deallocate the collection.
 */
- (void)dealloc {
	[dataDictionary release];
	[path release];
	[super dealloc];
}

@end
